﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Inovout.Caching;
using Inovout.Descriptor;
using Inovout.Lambda;

namespace Inovout.Data.Repository.Linq
{
    public static class QueryableOrderExtensions
    {
        public static IQueryable<TModel> Order<TModel>(this IQueryable<TModel> query, IEnumerable<OrderCriteria> orders) where TModel : class
        {
            return OrderExecutor<TModel>.Execute(query,orders);
        }
        private static class OrderExecutor<TModel> where TModel : class
        {
            internal static IQueryable<TModel> Execute(IQueryable<TModel> query, IEnumerable<OrderCriteria> orderCriterias)
            {
                foreach (var orderCriteria in orderCriterias)
                {
                    query = CachedExpressionCompiler.Process<IQueryable<TModel>, IQueryable<TModel>>(BuildOrderExpression(orderCriteria))(query);
                }
                return query;
            }


            private static Expression<Func<IQueryable<TModel>, IQueryable<TModel>>> BuildOrderExpression(OrderCriteria orderCriteria)
            {
                Type entityType = typeof(TModel);
                Type queryType = typeof(IQueryable<TModel>);

                ParameterExpression typeExpression = Expression.Parameter(entityType, "entity");
                ParameterExpression queryExpression = Expression.Parameter(queryType, "euery");

                LambdaExpression propertyAccessorExpression = PropertyDescriptor.BuildPropertyAccessorExpression(typeExpression, orderCriteria.OrderName);
                Type[] typeArgs = new Type[] { entityType, propertyAccessorExpression.Body.Type };

                MethodCallExpression orderExpression =
                    Expression.Call(typeof(Queryable), orderCriteria.OrderAscending ? "OrderBy" : "OrderByDescending", typeArgs, queryExpression, propertyAccessorExpression);


                return Expression.Lambda<Func<IQueryable<TModel>, IQueryable<TModel>>>(orderExpression, queryExpression);
            }
        }
    }
}
